package org.iamd.domain.data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.iamd.domain.business.ApplicationException;
import org.iamd.security.DefPermissions;
import org.iamd.security.ISecurityManager;
import org.iamd.security.SecurityException;
import org.iamd.security.SecurityServicesManager;
import org.iamd.util.CommonMsgKeys;
import org.iamd.util.Messenger;

/**
 * @see IDataAccess
 * 
 * @param <D>
 *            Data обекти, с които Layer-а работи
 * @param <C>
 *            Критерии за конкретния Data Access Layer
 * 
 * @author И. Атанасов
 */
public abstract class DataAccess<D extends IData, C extends Object> implements IDataAccess<D, C> {

	/** Инстанция на мениджъра на Сигурността. */
	private ISecurityManager secMng;

	/**
	 * Инициализация на инстанциалните полета.
	 * 
	 * @throws ApplicationException
	 *             при неуспешно инстанциране
	 */
	protected DataAccess() throws ApplicationException {
		try {
			this.secMng = SecurityServicesManager.getInstance().getService(ISecurityManager.class);
		} catch (SecurityException e) {
			throw new ApplicationException(CommonMsgKeys.M0016_DAO_INSTANTIATING_FAILED,
					new Object[] { getClass() }, e);
		}
	}

	/**
	 * @see org.iamd.domain.data.IDataAccess#beginWork()
	 */
	@Override
	public abstract void beginWork() throws DataAccessException;

	/**
	 * @see org.iamd.domain.data.IDataAccess#commitWork()
	 */
	@Override
	public abstract void commitWork() throws DataAccessException;

	/**
	 * @see org.iamd.domain.data.IDataAccess#createCriteria()
	 */
	@Override
	public abstract C createCriteria();

	/**
	 * @see org.iamd.domain.data.IDataAccess#createInstance()
	 */
	@Override
	public final D createInstance() throws DataAccessException {
		try {
			return getDataClazz().newInstance();
		} catch (Exception e) {
			throw new DataAccessException(CommonMsgKeys.M0002_CREATE_VO_FAILED,
					new Object[] { getDataClazz().getName() }, e);
		}
	}

	/**
	 * Преди същинската операция се извършва проверка дали текущия потребител има право да трие
	 * обекта, и ако няма се хвърля изключение.
	 * 
	 * @see org.iamd.domain.data.IDataAccess#delete(org.iamd.domain.data.IData)
	 */
	@Override
	public void delete(D data) throws DataAccessException {
		try {
			this.secMng.checkPermission(data, DefPermissions.DELETE);
			deleteObject(data);
		} catch (SecurityException e) {
			throw new DataAccessException(CommonMsgKeys.M0017_VO_DELETE_FAILED, new Object[] {
					data.getClass(), data.getId() }, e);
		}
	}

	/**
	 * При валиден критерий получения списък с резултати се филтрира на базата на правата на
	 * потребителя.
	 * 
	 * @see org.iamd.domain.data.IDataAccess#findByCriteria(java.lang.Object)
	 */
	@Override
	public List<D> findByCriteria(C criteria) throws DataAccessException {
		List<D> result = new ArrayList<D>();
		if (criteria != null) {
			List<D> resultList = findObjectsByCriteria(criteria);
			for (int i = 0; i < resultList.size(); i++) {
				try {
					this.secMng.checkPermission(resultList.get(i), DefPermissions.READ);
				} catch (SecurityException e) {
					resultList.remove(i--);
				}
			}
			result.addAll(resultList);
		}
		return result;
	}

	/**
	 * Преди същинската операция се извършва проверка дали текущия потребител има право да чете
	 * обекта. Ако няма това право като резултат се връща <code>null</code>.
	 * 
	 * @see org.iamd.domain.data.IDataAccess#findById(Serializable)
	 */
	@Override
	public D findById(Serializable id) throws DataAccessException {
		try {
			D result = findObjectById(id);
			this.secMng.checkPermission(result, DefPermissions.READ);
			return result;
		} catch (SecurityException e) {
			Messenger.logError(CommonMsgKeys.M0018_VO_READ_FAILED, new Object[] { getClass(), id }, e);
			return null;
		}
	}

	/**
	 * @see org.iamd.domain.data.IDataAccess#rollback()
	 */
	@Override
	public abstract void rollback() throws DataAccessException;

	/**
	 * @see org.iamd.domain.data.IDataAccess#save(org.iamd.domain.data.IData)
	 */
	@Override
	public void save(D data) throws DataAccessException {
		try {
			this.secMng.checkPermission(data, DefPermissions.CREATE);
			saveObject(data);
		} catch (SecurityException e) {
			throw new DataAccessException(CommonMsgKeys.M0019_VO_SAVE_FAILED, new Object[] {
					data.getClass(), data.getId() }, e);
		}
	}

	/**
	 * @see org.iamd.domain.data.IDataAccess#update(org.iamd.domain.data.IData)
	 */
	@Override
	public void update(D data) throws DataAccessException {
		try {
			this.secMng.checkPermission(data, DefPermissions.MODIFY);
			updateObject(data);
		} catch (SecurityException e) {
			throw new DataAccessException(CommonMsgKeys.M0020_VO_UPDATE_FAILED, new Object[] {
					data.getClass(), data.getId() }, e);
		}
	}

	/**
	 * Изтрива обект от repository на данни.
	 * 
	 * @param data
	 *            обектът, който трябва да бъде изтрит
	 * 
	 * @throws DataAccessException
	 *             при неуспешно изтриване
	 */
	protected abstract void deleteObject(D data) throws DataAccessException;

	/**
	 * Намира обект по системен идентификатор.
	 * 
	 * @param id
	 *            системен идентификатор на обекта
	 * 
	 * @return намереният обект или <code>null</code> ако не е намерен
	 * 
	 * @throws DataAccessException
	 *             при изключение възникнало при интеракция с репозиторито
	 * 
	 */
	protected abstract D findObjectById(Serializable id) throws DataAccessException;

	/**
	 * Намира списък от обекти по подадени критерии.
	 * 
	 * @param criteria
	 *            входни критерии
	 * 
	 * @return списък с обекти, които съответстват на критериите
	 * 
	 * @throws DataAccessException
	 *             при изключение възникнало при интеракция с репозиторито
	 */
	protected abstract List<D> findObjectsByCriteria(C criteria) throws DataAccessException;

	/**
	 * @return връща класа на VO-то, което е асоциирано с DAO-то
	 */
	protected abstract Class<D> getDataClazz();

	/**
	 * Записва обект в repository на данни.
	 * 
	 * @param data
	 *            обектът, който трябва да бъде съхранен
	 * 
	 * @throws DataAccessException
	 *             при неуспешен запис
	 */
	protected abstract void saveObject(D data) throws DataAccessException;

	/**
	 * Актуализира обект в repository на данни.
	 * 
	 * @param data
	 *            обектът, който трябва да бъде обновен
	 * 
	 * @throws DataAccessException
	 *             при неуспешен update
	 */
	protected abstract void updateObject(D data) throws DataAccessException;

}